package net.sf.fmj.ejmf.toolkit.media;

import java.util.Vector;

import javax.media.ClockStartedError;
import javax.media.ClockStoppedException;
import javax.media.Control;
import javax.media.Controller;
import javax.media.ControllerClosedEvent;
import javax.media.ControllerErrorEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataStarvedEvent;
import javax.media.DeallocateEvent;
import javax.media.EndOfMediaEvent;
import javax.media.IncompatibleTimeBaseException;
import javax.media.MediaTimeSetEvent;
import javax.media.NotPrefetchedError;
import javax.media.NotRealizedError;
import javax.media.PrefetchCompleteEvent;
import javax.media.RateChangeEvent;
import javax.media.RealizeCompleteEvent;
import javax.media.RestartingEvent;
import javax.media.StartEvent;
import javax.media.StopAtTimeEvent;
import javax.media.StopByRequestEvent;
import javax.media.StopEvent;
import javax.media.StopTimeChangeEvent;
import javax.media.Time;
import javax.media.TimeBase;
import javax.media.TransitionEvent;

import net.sf.fmj.ejmf.toolkit.controls.RateControl;

/**
 * The AbstractController class provides a basic implementation of a
 * javax.media.Controller. Subclasses should implement the following abstract
 * "do" methods to transition their Controller:
 * <p>
 * <UL>
 * <LI>doRealize()</LI>
 * <LI>doPrefetch()</LI>
 * <LI>doSyncStart()</LI>
 * <LI>doDeallocate()</LI>
 * <LI>doStop()</LI>
 * </UL>
 * <p>
 * Follow these rules when implementing these methods:
 * <p>
 * <OL>
 * <LI>Do not return until the state change is complete. Once the state change
 * is complete, return ASAP.</LI>
 * <p>
 * <LI>Do not call one another. They will be called in the correct order at the
 * correct time.</LI>
 * <p>
 * <LI>Do not set the current or target states. They are set automatically.</LI>
 * <p>
 * <LI>Do not post any TransitionEvents. They are posted automatically.</LI>
 * <p>
 * <LI>Do not call any of the Clock routines. They will be called automatically.
 * </LI>
 * <p>
 * <LI>Return true if successful. If unsuccessful, post an appropriate
 * ControllerErrorEvent and return false.</LI>
 * <p>
 * <LI>When the end of the media has been reached, call endOfMedia(). This will
 * post an EndOfMediaEvent and set the appropriate states. Do not post an
 * EndOfMediaEvent in any other way.</LI>
 * </OL>
 * <p>
 * Other abstact methods that should be implemented are:
 * <p>
 * <UL>
 * <LI>doClose()</LI>
 * <LI>doSetMediaTime()</LI>
 * <LI>doSetRate()</LI>
 * </UL>
 * 
 * <p>
 * From the book: Essential JMF, Gordon, Talley (ISBN 0130801046). Used with
 * permission.
 * </p>
 * 
 * @see AbstractPlayer
 * 
 * @author Steve Talley
 */

public abstract class AbstractController extends AbstractClock implements
		Controller {
	private int previousState;
	private int currentState = Unrealized;
	private int targetState;

	private StopTimeMonitor stopTimeMonitor;
	private ControllerEventQueue eventqueue;
	private ThreadQueue threadqueue;

	private Vector controls = new Vector();
	private Vector listeners = new Vector();

	/**
	 * Construct a AbstractController.
	 */
	public AbstractController() {
		super();
		eventqueue = new ControllerEventQueue(listeners,
				"ControllerEventQueue for " + this);
		stopTimeMonitor = new StopTimeMonitor(this, "StopTimeMonitor for "
				+ this);
		threadqueue = new ThreadQueue("ThreadQueue for " + this);
		// TODO: do not start in constructor, and stop somewhere
		// constructor sometimes gets called for a controller that will never be
		// used, and these threads just hang around.
		eventqueue.start();
		stopTimeMonitor.start();
		threadqueue.start();
		addControl(new RateControl(this));
	}

	// //////////////////////////////////////////////////////////
	//
	// Abstract methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Implement to realize the Controller.
	 * <p>
	 * This method should not be called directly. Instead, call realize().
	 * 
	 * @return True if successful, false otherwise.
	 */
	public abstract boolean doRealize();

	/**
	 * Implement to prefetch the Controller.
	 * <p>
	 * This method should not be called directly. Instead, call prefetch().
	 * 
	 * @return True if successful, false otherwise.
	 */
	public abstract boolean doPrefetch();

	/**
	 * Implement to start the Controller.
	 * <p>
	 * This method should not be called directly. Instead, call syncStart().
	 * 
	 * @return True if successful, false otherwise.
	 */
	public abstract boolean doSyncStart(Time t);

	/**
	 * Implement to deallocate the Controller.
	 * <p>
	 * This method should not be called directly. Instead, call prefetch().
	 * 
	 * @return True if successful, false otherwise.
	 */
	public abstract boolean doDeallocate();

	/**
	 * Implement to stop the Controller.
	 * <p>
	 * This method should not be called directly. Instead, call stop().
	 * 
	 * @return True if successful, false otherwise.
	 */
	public abstract boolean doStop();

	/**
	 * Close the Controller. Typically this method will release as many
	 * resources as possible, especially those that may be needed by other
	 * Controllers.
	 * <p>
	 * This method should not be called directly. Instead, call close().
	 */
	public abstract void doClose();

	/**
	 * Override to provide implementation-specific functionality. When this
	 * method is called, it is guaranteed that the Controller is Stopped and
	 * that the given time is within the Controller's duration.
	 * <p>
	 * This method should not be called directly. Instead, call setMediaTime().
	 * 
	 * @param t
	 *            The media time to set
	 */
	public abstract void doSetMediaTime(Time t);

	/**
	 * Override to provide implementation-specific functionality. When this
	 * method is called, it is guaranteed that the Controller is Stopped.
	 * <p>
	 * This method should not be called directly. Instead, call setRate().
	 * 
	 * @param rate
	 *            The requested rate to set
	 * 
	 * @return The actual rate that was set
	 */
	public abstract float doSetRate(float rate);

	// //////////////////////////////////////////////////////
	//
	// javax.media.Clock methods
	//
	// //////////////////////////////////////////////////////

	/**
	 * Set the <code>TimeBase</code> for this <code>Clock</code>. This method
	 * can only be called on a <i>Stopped</i> <code>Clock</code>. A
	 * <code>ClockStartedError</code> is thrown if <code>setTimeBase</code> is
	 * called on a <i>Started</i> <code>Clock</code>.
	 * <p>
	 * A <code>Clock</code> has a default <code>TimeBase</code> that is
	 * determined by the implementation. To reset a <code>Clock</code> to its
	 * default <code>TimeBase</code>, call <code>setTimeBase(null)</code>.
	 * 
	 * @param timebase
	 *            The new <CODE>TimeBase</CODE> or <CODE>null</CODE> to reset
	 *            the <code>Clock</code> to its default <code>TimeBase</code>.
	 * 
	 * @exception IncompatibleTimeBaseException
	 *                Thrown if the <code>Clock</code> can't use the specified
	 *                <code>TimeBase</code>.
	 */
	public synchronized void setTimeBase(TimeBase timebase)
			throws IncompatibleTimeBaseException {
		if (currentState == Unrealized || currentState == Realizing) {
			throw new NotRealizedError(
					"Cannot set TimeBase on an Unrealized Controller.");
		}

		super.setTimeBase(timebase);
	}

	/**
	 * Get the TimeBase that this Controller is using.
	 */
	public synchronized TimeBase getTimeBase() {
		if (currentState == Unrealized || currentState == Realizing) {
			throw new NotRealizedError(
					"Cannot get time base from an Unrealized Controller");
		}
		return super.getTimeBase();
	}

	/**
	 * Sets the stop time for this AbstractController. Posts a
	 * StopTimeChangeEvent if the stop time given is different than the current
	 * stop time.
	 * 
	 * @param mediaStopTime
	 *            The time at which you want the <code>Clock</code> to stop, in
	 *            <i>media time</i>.
	 * 
	 * @exception NotRealizedError
	 *                If the Controller is not Realized.
	 * 
	 * @exception ClockStartedError
	 *                If the Controller is Started.
	 */
	public synchronized void setStopTime(Time mediaStopTime) {
		if (currentState == Unrealized || currentState == Realizing) {
			throw new NotRealizedError(
					"Cannot set stop time on an unrealized Controller");
		}

		Time oldStopTime = getStopTime();

		// If the stop time has changed, post an event
		if (mediaStopTime.getNanoseconds() != oldStopTime.getNanoseconds()) {
			// Set in superclass
			super.setStopTime(mediaStopTime);

			// Post event
			postEvent(new StopTimeChangeEvent(this, mediaStopTime));
		}
	}

	/**
	 * Sets the media time.
	 * 
	 * @param t
	 *            The media time to set
	 * 
	 * @exception NotRealizedError
	 *                If the Controller is not Realized.
	 * 
	 * @exception ClockStartedError
	 *                If the Controller is Started.
	 */
	public synchronized void setMediaTime(Time t) {
		if (currentState == Unrealized || currentState == Realizing) {
			throw new NotRealizedError(
					"Cannot set media time on an Unrealized Controller");
		}

		long nano = t.getNanoseconds();
		Time duration = getDuration();

		// Enforce upper bound on start time
		if (duration != DURATION_UNKNOWN && duration != DURATION_UNBOUNDED) {
			long limit = duration.getNanoseconds();
			if (nano > limit) {
				t = new Time(limit);
			}
		}

		// Set the media time
		super.setMediaTime(t);

		// Call implementation-specific functionality
		doSetMediaTime(t);

		// Post MediaTimeSetEvent
		postEvent(new MediaTimeSetEvent(this, t));
	}

	/**
	 * Calculates the current media time based on the current time-base time,
	 * the time-base start time, the media start time, and the rate.
	 * 
	 * @return The current media time
	 */
	public synchronized Time getMediaTime() {
		Time mediaTime = super.getMediaTime();
		Time duration = getDuration();

		// Compare media time with duration
		if (duration != DURATION_UNKNOWN && duration != DURATION_UNBOUNDED
				&& mediaTime.getNanoseconds() > duration.getNanoseconds()) {
			return duration;
		}

		return mediaTime;
	}

	/**
	 * Set the temporal scale factor. The argument <i>suggests</i> the scale
	 * factor to use.
	 * <p>
	 * The <code>setRate</code> method returns the actual rate set by the
	 * <code>Clock</code>. <code>Clocks</code> should set their rate as close to
	 * the requested value as possible, but are not required to set the rate to
	 * the exact value of any argument other than 1.0. A <code>Clock</code> is
	 * only guaranteed to set its rate exactly to 1.0.
	 * 
	 * @param rate
	 *            The temporal scale factor (rate) to set.
	 * 
	 * @exception NotRealizedError
	 *                If the Controller is not Realized.
	 * 
	 * @exception ClockStartedError
	 *                If the Controller is Started.
	 * 
	 * @return The actual rate set.
	 * 
	 */
	public synchronized float setRate(float rate) {
		if (currentState == Unrealized || currentState == Realizing) {
			throw new NotRealizedError(
					"Cannot set rate on an Unrealized Controller.");
		}

		// Save the current rate
		float oldRate = getRate();

		// Enforce superclass reqs
		float superRate = super.setRate(rate);

		// Set the rate in the subclass
		float subRate = doSetRate(superRate);

		// If the rate has changed since setting in the
		// superclass, set it agagin
		if (rate != 1.0F && superRate != subRate) {
			superRate = super.setRate(subRate);

			// If it has changed again, give up and set to the
			// only rate guaranteed to be accepted
			if (superRate != subRate) {
				return setRate(1.0F);
			}
		}

		// If the rate has changed, commit it and post an event.
		if (superRate != oldRate) {
			postEvent(new RateChangeEvent(this, superRate));
		}

		return superRate;
	}

	// //////////////////////////////////////////////////////////
	//
	// javax.media.Duration methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Returns DURATION_UNKNOWN. This method should be overridden to report a
	 * more precise duration.
	 */
	public Time getDuration() {
		return DURATION_UNKNOWN;
	}

	// //////////////////////////////////////////////////////////
	//
	// javax.media.Controller methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Realize Controller on new thread. Subclasses should override doRealize()
	 * to do the actual work to transition the Controller.
	 * <p>
	 * Checks for Controller state prerequisites and creates a RealizeThread to
	 * realize the AbstractController. If there is already a thread
	 * transitioning the AbstractController forward, then the target state of
	 * the AbstractController is set to Realized and the method returns.
	 * <p>
	 * Asynchronous method -- Start synchronous transition on another thread and
	 * return ASAP.
	 */
	public final synchronized void realize() {
		// Has this state already been reached?
		if (currentState >= Realized) {
			postRealizeCompleteEvent();
			return;
		}

		// Set the target state
		if (targetState < Realized) {
			setTargetState(Realized);
		}

		// Realize on a separate thread
		Thread thread = new Thread() {
			public void run() {
				if (AbstractController.this.getState() < Realized) {
					synchronousRealize();
				}
			}
		};

		threadqueue.addThread(thread);
	}

	/**
	 * Prefetch Controller on new thread. Subclasses should override
	 * doPrefetch() to do the actual work to transition the Controller.
	 * <p>
	 * Checks for Controller state prerequisites and creates a PrefetchThread to
	 * prefetch the AbstractController. If there is already a thread
	 * transitioning the AbstractController forward, then the target state of
	 * the AbstractController is set to Prefetched and the method returns.
	 * <p>
	 * Asynchronous method -- Start synchronous transition on another thread and
	 * return ASAP.
	 */
	public final synchronized void prefetch() {
		// Has this state already been reached?
		if (currentState >= Prefetched) {
			postPrefetchCompleteEvent();
			return;
		}

		// Set the target state
		if (targetState < Prefetched) {
			setTargetState(Prefetched);
		}

		// Prefetch on a separate thread
		Thread thread = new Thread() {
			public void run() {
				if (AbstractController.this.getState() < Prefetched) {
					synchronousPrefetch();
				}
			}
		};

		threadqueue.addThread(thread);
	}

	/**
	 * SyncStart Controller on new thread. Subclasses should override
	 * doSyncStart() to do the actual work to transition the Controller.
	 * <p>
	 * Checks for Controller state prerequisites and creates a SyncStartThread
	 * to syncstart the AbstractController. The target state of the
	 * AbstractController is then set to Started and the thread is started.
	 * <p>
	 * Asynchronous method -- Start synchronous transition on another thread and
	 * return ASAP.
	 */
	public final synchronized void syncStart(final Time t) {
		// Enforce state prereqs
		if (currentState == Started) {
			throw new ClockStartedError(
					"syncStart() cannot be called on a started Clock");
		}

		// Enforce state prereqs
		if (currentState != Prefetched) {
			throw new NotPrefetchedError(
					"Cannot start the Controller before it has been prefetched");
		}

		// Set the target state
		setTargetState(Started);

		// SyncStart on a separate thread
		Thread thread = new Thread() {
			public void run() {
				if (AbstractController.this.getState() < Started) {
					synchronousSyncStart(t);
				}
			}
		};

		threadqueue.addThread(thread);
	}

	/**
	 * Deallocate Controller on current thread. Subclasses should override
	 * doDeallocate() to do the actual work to transition the Controller. After
	 * ensuring state prerequisites, this method will call doDeallocate(). If
	 * doDeallocate() returns true, then the Controller is placed in the
	 * appropriate state and a DeallocateCompleteEvent is posted. Otherwise, it
	 * is assumed that the controller has posted a ControllerErrorEvent
	 * detailing the reasons for it's failure.
	 * <p>
	 * Synchronous method -- return when transition complete
	 */
	public final synchronized void deallocate() {
		int state;

		// Enforce state prereq
		if (currentState == Started) {
			throw new ClockStartedError(
					"deallocate() cannot be called on a started Controller");
		}

		// Kill any forward-transitioning thread
		if (threadqueue != null) // may be already stopped an nulled by close()
			threadqueue.stopThreads();

		// Do the actual deallocating. If this returns false,
		// the deallocate was unsuccessful. Rely on the Controller
		// to post the ControllerErrorEvent and return without
		// modifying the current or target states.

		if (doDeallocate()) {

			// The deallocate was successful

			// Return to previous state as dictated by the spec
			if (currentState == Unrealized || currentState == Realizing) {
				state = Unrealized;
			} else {
				state = Realized;
			}

			// Set current and target states and post event
			setState(state);
			setTargetState(state);
			postDeallocateEvent();
		}
	}

	/**
	 * Stop Controller on current thread and post a StopByRequestEvent.
	 * Subclasses should override doStop() to do the actual work to stop the
	 * Controller.
	 */
	public final void stop() {
		if (stopController()) {
			postStopByRequestEvent();
		}
	}

	/**
	 * Stop Controller on current thread and post a StopAtTimeEvent. Subclasses
	 * should override doStop() to do the actual work to stop the Controller.
	 * <p>
	 * This method is usually only called (indirectly) by the StopTimeMonitor
	 * class.
	 * <p>
	 * Synchronous method -- return when transition complete
	 */
	protected void stopAtTime() {
		if (stopController()) {
			postStopAtTimeEvent();
		}
	}

	/**
	 * Stop Controller on current thread and post a RestartingEvent. Subclasses
	 * should override doStop() to do the actual work to stop the Controller.
	 * <p>
	 * This method is usually only called (indirectly) by Player.setMediaTime()
	 * or Player.setRate() when a managed Controller must be stopped before its
	 * media time and rate, respectively, can be set.
	 * <p>
	 * Synchronous method -- return when transition complete
	 */
	protected void stopInRestart() {
		if (stopController()) {
			postRestartingEvent();
		}
	}

	/**
	 * Stop the controller. If the Controller is Realizing or Prefetching, then
	 * the target state will be set to Realized or Prefetched, respectively, and
	 * the Controller will stop when it completes the transition. If the
	 * Controller is Started, this method will call doStop(). If doStop()
	 * returns true, then the Controller is placed in the Prefetched state and a
	 * StopEvent is posted. If doStop() returns false, it is assumed that the
	 * controller has posted a ControllerErrorEvent detailing the reasons for
	 * it's failure.
	 * <p>
	 * Synchronous method -- return when transition complete
	 * 
	 * @return boolean indicating whether the stop was successful.
	 */
	protected synchronized boolean stopController() {
		// Kill any forward-transitioning threads.
		if (threadqueue != null)
			threadqueue.stopThreads();

		switch (currentState) {

		// Stop any scheduled forward transitions

		case Unrealized:
		case Realized:
		case Prefetched:
			setTargetState(currentState);
			return true;

			// If the Controller was Realizing, return it to the
			// Unrealized state.

		case Realizing:
			setState(Unrealized);
			setTargetState(Unrealized);
			return true;

			// If the Controller was Prefetching, return it to
			// the Realized state.

		case Prefetching:
			setState(Realized);
			setTargetState(Realized);
			return true;
		}

		// If we are here, then the Controller is Started

		// Do the actual stopping. If this returns false, the
		// stop was unsuccessful. Rely on the Controller to post the
		// ControllerErrorEvent and return without modifying the
		// current or target states.

		if (!doStop()) {
			return false;
		}

		// The stop was successful

		// Stop the Clock
		super.stop();

		// Set state. The StopEvent will be posted by
		// one of the protected synchronous Stop methods
		setState(Prefetched);
		setTargetState(Prefetched);

		return true;
	}

	/**
	 * Close the Controller. Release resources held by this Controller.
	 * Subclasses should implement doClose() to add additional functionality.
	 */
	public synchronized final void close() {

		// Stop the Controller in case it is Started
		stop();

		// Call implementation-specific functionality
		doClose();

		// Set some resources to null
		controls = null;
		threadqueue = null;

		// Post a ControllerClosedEvent
		postControllerClosedEvent();
	}

	// //////////////////////////////////////////////////////////
	//
	// Control methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Add a Control to this Controller.
	 * 
	 * @param newControl
	 *            The Control to add.
	 */
	public void addControl(Control newControl) {
		synchronized (controls) {
			if (!controls.contains(newControl)) {
				controls.addElement(newControl);
			}
		}
	}

	/**
	 * Remove a Control from this Controller.
	 * 
	 * @param oldControl
	 *            The Control to remove.
	 */
	public void removeControl(Control oldControl) {
		controls.removeElement(oldControl);
	}

	/**
	 * Get a list of the Control objects that this Controller supports. If there
	 * are no controls, an array of length zero is returned.
	 * 
	 * @return A list of Controller Controls.
	 * 
	 */
	public Control[] getControls() {
		Control[] array;
		synchronized (controls) {
			array = new Control[controls.size()];
			controls.copyInto(array);
		}
		return array;
	}

	/**
	 * Get the Control that supports the class or interface specified. The full
	 * class or interface name should be specified. Null is returned if the
	 * Control is not supported.
	 * 
	 * @return Control for the given class or interface name, or null if no such
	 *         Control is supported.
	 */
	public Control getControl(String forName) {
		Class c;

		try {
			c = Class.forName(forName);
		} catch (Exception e) {
			return null;
		}

		synchronized (controls) {
			for (int i = 0, n = controls.size(); i < n; i++) {
				Control control = (Control) controls.elementAt(i);
				if (c.isInstance(control)) {
					return control;
				}
			}
		}

		return null;
	}

	// //////////////////////////////////////////////////////////
	//
	// Listener methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Specify a ControllerListener to which this Controller will send events.
	 * 
	 * @param listener
	 *            The listener to which the Controller will post events.
	 */
	public void addControllerListener(ControllerListener listener) {
		synchronized (listeners) {
			if (!listeners.contains(listener)) {
				listeners.addElement(listener);
			}
		}
	}

	/**
	 * Remove the specified listener from this Controller's listener list.
	 * 
	 * @param listener
	 *            The listener that has been receiving events from this
	 *            Controller.
	 * 
	 */
	public void removeControllerListener(ControllerListener listener) {
		synchronized (listeners) {
			listeners.removeElement(listener);
		}
	}

	// //////////////////////////////////////////////////////////
	//
	// State methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Get the previous state of this Controller.
	 */
	public int getPreviousState() {
		return previousState;
	}

	/**
	 * Set the current state of this Controller. This will implicitly set the
	 * previous state as well.
	 */
	protected synchronized void setState(int state) {
		if (state == currentState)
			return;
		previousState = currentState;
		currentState = state;
	}

	/**
	 * Get the current state of this Controller.
	 */
	public int getState() {
		return currentState;
	}

	/**
	 * Set the targetState state of this Controller.
	 */
	protected void setTargetState(int state) {
		targetState = state;
	}

	/**
	 * Get the target state of this Controller.
	 */
	public int getTargetState() {
		return targetState;
	}

	// //////////////////////////////////////////////////////////
	//
	// AbstractController methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Indicates to the framework that the end of media has been reached. Marks
	 * the media time, sets the current and target states to Prefetched, and
	 * posts an EndOfMediaEvent.
	 * 
	 * @exception ClockStoppedException
	 *                If the AbstractController is not in the Started state.
	 */
	protected synchronized void endOfMedia() throws ClockStoppedException {
		// Enforce state prereq
		if (currentState != Started) {
			throw new ClockStoppedException();
		}

		// Stop the Clock
		super.stop();

		// Set the state and post an EndOfMediaEvent
		setState(Prefetched);
		setTargetState(Prefetched);
		postEndOfMediaEvent();
	}

	// //////////////////////////////////////////////////////////
	//
	// Event methods
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Post a ControllerEvent to the Media Event Queue
	 * 
	 * @param event
	 *            The ControllerEvent to post.
	 */
	protected void postEvent(ControllerEvent event) {
		eventqueue.postEvent(event);
	}

	/**
	 * Post a TransitionEvent to the Media Event Queue. Automatically fill in
	 * the Previous State, Current State, and Target State properties of the
	 * TransitionEvent.
	 */
	protected void postTransitionEvent() {
		postEvent(new TransitionEvent(this, previousState, currentState,
				targetState));
	}

	/**
	 * Post a RealizeCompleteEvent to the Media Event Queue. Automatically fill
	 * in the Previous State, Current State, and Target State properties of the
	 * RealizeCompleteEvent.
	 */
	protected void postRealizeCompleteEvent() {
		postEvent(new RealizeCompleteEvent(this, previousState, currentState,
				targetState));
	}

	/**
	 * Post a PrefetchCompleteEvent to the Media Event Queue. Automatically fill
	 * in the Previous State, Current State, and Target State properties of the
	 * PrefetchCompleteEvent.
	 */
	protected void postPrefetchCompleteEvent() {
		postEvent(new PrefetchCompleteEvent(this, previousState, currentState,
				targetState));
	}

	/**
	 * Post a DeallocateEvent to the Media Event Queue. Automatically fill in
	 * the Previous State, Current State, Target State, and Media Time
	 * properties of the DeallocateEvent.
	 */
	protected void postDeallocateEvent() {
		postEvent(new DeallocateEvent(this, previousState, currentState,
				targetState, getMediaTime()));
	}

	/**
	 * Post a StopEvent to the Media Event Queue. Automatically fill in the
	 * Previous State, Current State, Target State, and Media Time properties of
	 * the StopEvent.
	 */
	protected void postStopEvent() {
		postEvent(new StopEvent(this, previousState, currentState, targetState,
				getMediaTime()));
	}

	/**
	 * Post a StopAtTimeEvent to the Media Event Queue. Automatically fill in
	 * the Previous State, Current State, Target State, and Media Time
	 * properties of the StopAtTimeEvent.
	 */
	protected void postStopAtTimeEvent() {
		postEvent(new StopAtTimeEvent(this, previousState, currentState,
				targetState, getMediaTime()));
	}

	/**
	 * Post a StartEvent to the Media Event Queue. Automatically fill in the
	 * Previous State, Current State, Target State, Media Time, and Time-base
	 * Time properties of the StartEvent.
	 */
	protected void postStartEvent() {
		postEvent(new StartEvent(this, previousState, currentState,
				targetState, getMediaStartTime(), getTimeBaseStartTime()));
	}

	/**
	 * Post a DataStarvedEvent to the Media Event Queue. Automatically fill in
	 * the Previous State, Current State, Target State, and Media Time
	 * properties of the DataStarvedEvent.
	 */
	protected void postDataStarvedEvent() {
		postEvent(new DataStarvedEvent(this, previousState, currentState,
				targetState, getMediaTime()));
	}

	/**
	 * Post a EndOfMediaEvent to the Media Event Queue. Automatically fill in
	 * the Previous State, Current State, Target State, and Media Time
	 * properties of the EndOfMediaEvent.
	 */
	protected void postEndOfMediaEvent() {
		postEvent(new EndOfMediaEvent(this, previousState, currentState,
				targetState, getMediaTime()));
	}

	/**
	 * Post a RestartingEvent to the Media Event Queue. Automatically fill in
	 * the Previous State, Current State, Target State, and Media Time
	 * properties of the RestartingEvent.
	 */
	protected void postRestartingEvent() {
		postEvent(new RestartingEvent(this, previousState, currentState,
				targetState, getMediaTime()));
	}

	/**
	 * Post a StopByRequestEvent to the Media Event Queue. Automatically fill in
	 * the Previous State, Current State, Target State, and Media Time
	 * properties of the StopByRequestEvent.
	 */
	protected void postStopByRequestEvent() {
		postEvent(new StopByRequestEvent(this, previousState, currentState,
				targetState, getMediaTime()));
	}

	/**
	 * Post a ControllerClosedEvent to the Media Event Queue.
	 */
	protected void postControllerClosedEvent() {
		postEvent(new ControllerClosedEvent(this));
	}

	/**
	 * Post a ControllerClosedEvent to the Media Event Queue.
	 */
	protected void postControllerErrorEvent(String msg) {
		postEvent(new ControllerErrorEvent(this, msg));
	}

	// //////////////////////////////////////////////////////////
	//
	// Synchronous state-changing methods
	//
	// These routines are called by way of a TransitionThread.
	// When these methods are called, the following assumptions
	// can be made:
	//  
	// 1. The current state is less than the desired state. If
	// doSyncStart() is called, the Controller is guaranteed
	// to be in the Prefetched state.
	//  
	// 2. The target state is greater than or equal to the
	// desired state.
	//  
	// 3. Any state-related exceptions have been thrown already.
	//  
	// 4. There are no other state-changing threads running.
	//
	// //////////////////////////////////////////////////////////

	/**
	 * Gets the ThreadQueue object for this AbstractController.
	 */
	protected ThreadQueue getThreadQueue() {
		return threadqueue;
	}

	/**
	 * Realize the AbstractController synchronously.
	 * <p>
	 * This method should not be called directly. Instead, call realize().
	 * <p>
	 * Synchronous method -- return when transition complete
	 */
	protected void synchronousRealize() {
		// Set the current state and post event
		setState(Realizing);
		postTransitionEvent();

		// Do the actual realizing
		if (doRealize()) {

			// The realize was successful

			// Set the current state and post event
			setState(Realized);
			postRealizeCompleteEvent();

			// Set the initial rate
			setRate(1);

			// Set the initial media time
			setMediaTime(new Time(0));

		} else {

			// The realize was unsuccessful
			// Rely on the Controller to post the
			// ControllerErrorEvent

			// Reset the current and target states
			setState(Unrealized);
			setTargetState(Unrealized);
		}
	}

	/**
	 * Realize the AbstractController synchronously.
	 * <p>
	 * This method should not be called directly. Instead, call prefetch().
	 * <p>
	 * Synchronous method -- return when transition complete
	 */
	protected void synchronousPrefetch() {
		// Does the controller need to be realized?
		if (currentState < Realized) {
			synchronousRealize();

			if (currentState < Realized) // realized failed - do not proceed.
				return;
		}

		// Set the current state and post event
		setState(Prefetching);
		postTransitionEvent();

		// Do the actual prefetching
		if (doPrefetch()) {

			// The prefetch was successful

			// Set the current state and post event
			setState(Prefetched);
			postPrefetchCompleteEvent();

			// Set the initial media time
			setMediaTime(new Time(0));

		} else {

			// The prefetch was unsuccessful.
			// Rely on the Controller to post the
			// ControllerErrorEvent

			// Reset the current and target states
			setState(Realized);
			setTargetState(Realized);
		}
	}

	/**
	 * Returns LATENCY_UNKNOWN. This method should be overridden to report a
	 * more precise start latency.
	 */
	public Time getStartLatency() {
		if (currentState == Unrealized || currentState == Realizing) {
			throw new NotRealizedError(
					"Cannot get start latency from an unrealized Controller.");
		}

		return LATENCY_UNKNOWN;
	}

	/**
	 * SyncStart the AbstractController synchronously at the
	 * previously-specified time-base start time.
	 * <p>
	 * This method should not be called directly. Instead, call syncStart().
	 * <p>
	 * Synchronous method -- return when transition complete
	 */
	protected void synchronousSyncStart(Time t) {

		// Set the state and post event
		setState(Started);
		postStartEvent();

		// Calculate start latency. If unknown, asssume zero.
		Time latencyTime = getStartLatency();
		long latency;

		if (latencyTime == LATENCY_UNKNOWN) {
			latency = 0;
		} else {
			latency = latencyTime.getNanoseconds();
		}

		long start = t.getNanoseconds();
		long now = getTimeBase().getNanoseconds();

		// If the start time is in the past, change it to now
		if (now + latency > start)
			t = new Time(now + latency);

		// Start the clock
		super.syncStart(t);

		// Do the actual syncStarting
		if (!doSyncStart(t)) {

			// The syncStart was unsuccessful
			// Rely on the Controller to post the
			// ControllerErrorEvent

			// Reset the states
			setState(Prefetched);
			setTargetState(Prefetched);
		}
	}

	/**
	 * For a given time-base start time, block until the AbstractController
	 * should be started. For a given time- base start time (t), this method
	 * will get the AbstractController's start latency (l) and block until (t -
	 * l). This method is useful for implementations of doSyncStart().
	 * <p>
	 * If the time-base time (t - l) has already passed, return immediately.
	 */
	public void blockUntilStart(Time t) {

		// Calculate start latency. If unknown, asssume zero.
		Time latencyTime = getStartLatency();
		long latency;

		if (latencyTime == LATENCY_UNKNOWN) {
			latency = 0;
		} else {
			latency = latencyTime.getNanoseconds();
		}

		long start = t.getNanoseconds();
		long now = getTimeBase().getNanoseconds();
		long delay = (start - latency - now) / 1000000;

		// Wait for the syncTime

		if (delay > 0) {
			try {
				Thread.sleep(delay);
			} catch (InterruptedException e) {
			}
		}
	}
}
